/* eslint-disable no-console */
/* eslint-disable no-inner-declarations */
/* global THREE, $, getCookie, setCookie */

const TAU = 2 * Math.PI;
const EPSILON = 1e-3;
const skinLayout = [{
	head: [{
		l: {
			x: 16,
			y: 8,
			w: 8,
			h: 8
		},
		r: {
			x: 0,
			y: 8,
			w: 8,
			h: 8
		},
		u: {
			x: 8,
			y: 0,
			w: 8,
			h: 8
		},
		d: {
			x: 16,
			y: 7,
			w: 8,
			h: -8
		},
		f: {
			x: 8,
			y: 8,
			w: 8,
			h: 8
		},
		b: {
			x: 24,
			y: 8,
			w: 8,
			h: 8
		}
	},
	{
		l: {
			x: 48,
			y: 8,
			w: 8,
			h: 8
		},
		r: {
			x: 32,
			y: 8,
			w: 8,
			h: 8
		},
		u: {
			x: 40,
			y: 0,
			w: 8,
			h: 8
		},
		d: {
			x: 48,
			y: 7,
			w: 8,
			h: -8
		},
		f: {
			x: 40,
			y: 8,
			w: 8,
			h: 8
		},
		b: {
			x: 56,
			y: 8,
			w: 8,
			h: 8
		}
	}],
	torso: [{
		l: {
			x: 28,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 16,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 20,
			y: 16,
			w: 8,
			h: 4
		},
		d: {
			x: 28,
			y: 19,
			w: 8,
			h: -4
		},
		f: {
			x: 20,
			y: 20,
			w: 8,
			h: 12
		},
		b: {
			x: 32,
			y: 20,
			w: 8,
			h: 12
		}
	}],
	armR: [{
		l: {
			x: 48,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 16,
			w: 4,
			h: 4
		},
		d: {
			x: 48,
			y: 19,
			w: 4,
			h: -4
		},
		f: {
			x: 44,
			y: 20,
			w: 4,
			h: 12
		},
		b: {
			x: 52,
			y: 20,
			w: 4,
			h: 12
		}
	}],
	armRS: [{
		l: {
			x: 47,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 16,
			w: 3,
			h: 4
		},
		d: {
			x: 47,
			y: 19,
			w: 3,
			h: -4
		},
		f: {
			x: 44,
			y: 20,
			w: 3,
			h: 12
		},
		b: {
			x: 51,
			y: 20,
			w: 3,
			h: 12
		}
	}],
	armL: [{
		l: {
			x: 43,
			y: 20,
			w: -4,
			h: 12
		},
		r: {
			x: 51,
			y: 20,
			w: -4,
			h: 12
		},
		u: {
			x: 47,
			y: 16,
			w: -4,
			h: 4
		},
		d: {
			x: 51,
			y: 19,
			w: -4,
			h: -4
		},
		f: {
			x: 47,
			y: 20,
			w: -4,
			h: 12
		},
		b: {
			x: 55,
			y: 20,
			w: -4,
			h: 12
		}
	}],
	armLS: [{
		l: {
			x: 43,
			y: 20,
			w: -4,
			h: 12
		},
		r: {
			x: 50,
			y: 20,
			w: -4,
			h: 12
		},
		u: {
			x: 46,
			y: 16,
			w: -3,
			h: 4
		},
		d: {
			x: 49,
			y: 19,
			w: -3,
			h: -4
		},
		f: {
			x: 46,
			y: 20,
			w: -3,
			h: 12
		},
		b: {
			x: 53,
			y: 20,
			w: -3,
			h: 12
		}
	}],
	legR: [{
		l: {
			x: 8,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 0,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 4,
			y: 16,
			w: 4,
			h: 4
		},
		d: {
			x: 8,
			y: 19,
			w: 4,
			h: -4
		},
		f: {
			x: 4,
			y: 20,
			w: 4,
			h: 12
		},
		b: {
			x: 12,
			y: 20,
			w: 4,
			h: 12
		}
	}],
	legL: [{
		l: {
			x: 3,
			y: 20,
			w: -4,
			h: 12
		},
		r: {
			x: 11,
			y: 20,
			w: -4,
			h: 12
		},
		u: {
			x: 7,
			y: 16,
			w: -4,
			h: 4
		},
		d: {
			x: 11,
			y: 19,
			w: -4,
			h: -4
		},
		f: {
			x: 7,
			y: 20,
			w: -4,
			h: 12
		},
		b: {
			x: 15,
			y: 20,
			w: -4,
			h: 12
		}
	}]
},
{
	head: [{
		l: {
			x: 16,
			y: 8,
			w: 8,
			h: 8
		},
		r: {
			x: 0,
			y: 8,
			w: 8,
			h: 8
		},
		u: {
			x: 8,
			y: 0,
			w: 8,
			h: 8
		},
		d: {
			x: 16,
			y: 7,
			w: 8,
			h: -8
		},
		f: {
			x: 8,
			y: 8,
			w: 8,
			h: 8
		},
		b: {
			x: 24,
			y: 8,
			w: 8,
			h: 8
		}
	},
	{
		l: {
			x: 48,
			y: 8,
			w: 8,
			h: 8
		},
		r: {
			x: 32,
			y: 8,
			w: 8,
			h: 8
		},
		u: {
			x: 40,
			y: 0,
			w: 8,
			h: 8
		},
		d: {
			x: 48,
			y: 7,
			w: 8,
			h: -8
		},
		f: {
			x: 40,
			y: 8,
			w: 8,
			h: 8
		},
		b: {
			x: 56,
			y: 8,
			w: 8,
			h: 8
		}
	}],
	torso: [{
		l: {
			x: 28,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 16,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 20,
			y: 16,
			w: 8,
			h: 4
		},
		d: {
			x: 28,
			y: 19,
			w: 8,
			h: -4
		},
		f: {
			x: 20,
			y: 20,
			w: 8,
			h: 12
		},
		b: {
			x: 32,
			y: 20,
			w: 8,
			h: 12
		}
	},
	{
		l: {
			x: 28,
			y: 36,
			w: 4,
			h: 12
		},
		r: {
			x: 16,
			y: 36,
			w: 4,
			h: 12
		},
		u: {
			x: 20,
			y: 32,
			w: 8,
			h: 4
		},
		d: {
			x: 28,
			y: 35,
			w: 8,
			h: -4
		},
		f: {
			x: 20,
			y: 36,
			w: 8,
			h: 12
		},
		b: {
			x: 32,
			y: 36,
			w: 8,
			h: 12
		}
	}],
	armR: [{
		l: {
			x: 48,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 16,
			w: 4,
			h: 4
		},
		d: {
			x: 48,
			y: 19,
			w: 4,
			h: -4
		},
		f: {
			x: 44,
			y: 20,
			w: 4,
			h: 12
		},
		b: {
			x: 52,
			y: 20,
			w: 4,
			h: 12
		}
	},
	{
		l: {
			x: 48,
			y: 36,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 36,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 32,
			w: 4,
			h: 4
		},
		d: {
			x: 48,
			y: 35,
			w: 4,
			h: -4
		},
		f: {
			x: 44,
			y: 36,
			w: 4,
			h: 12
		},
		b: {
			x: 52,
			y: 36,
			w: 4,
			h: 12
		}
	}],
	armRS: [{
		l: {
			x: 47,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 16,
			w: 3,
			h: 4
		},
		d: {
			x: 47,
			y: 19,
			w: 3,
			h: -4
		},
		f: {
			x: 44,
			y: 20,
			w: 3,
			h: 12
		},
		b: {
			x: 51,
			y: 20,
			w: 3,
			h: 12
		}
	},
	{
		l: {
			x: 47,
			y: 36,
			w: 4,
			h: 12
		},
		r: {
			x: 40,
			y: 36,
			w: 4,
			h: 12
		},
		u: {
			x: 44,
			y: 32,
			w: 3,
			h: 4
		},
		d: {
			x: 47,
			y: 35,
			w: 3,
			h: -4
		},
		f: {
			x: 44,
			y: 36,
			w: 3,
			h: 12
		},
		b: {
			x: 51,
			y: 36,
			w: 3,
			h: 12
		}
	}],
	armL: [{
		l: {
			x: 40,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 32,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 36,
			y: 48,
			w: 4,
			h: 4
		},
		d: {
			x: 40,
			y: 51,
			w: 4,
			h: -4
		},
		f: {
			x: 36,
			y: 52,
			w: 4,
			h: 12
		},
		b: {
			x: 44,
			y: 52,
			w: 4,
			h: 12
		}
	},
	{
		l: {
			x: 56,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 48,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 52,
			y: 48,
			w: 4,
			h: 4
		},
		d: {
			x: 56,
			y: 51,
			w: 4,
			h: -4
		},
		f: {
			x: 52,
			y: 52,
			w: 4,
			h: 12
		},
		b: {
			x: 60,
			y: 52,
			w: 4,
			h: 12
		}
	}],
	armLS: [{
		l: {
			x: 39,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 32,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 36,
			y: 48,
			w: 3,
			h: 4
		},
		d: {
			x: 39,
			y: 51,
			w: 3,
			h: -4
		},
		f: {
			x: 36,
			y: 52,
			w: 3,
			h: 12
		},
		b: {
			x: 43,
			y: 52,
			w: 3,
			h: 12
		}
	},
	{
		l: {
			x: 55,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 48,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 52,
			y: 48,
			w: 3,
			h: 4
		},
		d: {
			x: 55,
			y: 51,
			w: 3,
			h: -4
		},
		f: {
			x: 52,
			y: 52,
			w: 3,
			h: 12
		},
		b: {
			x: 59,
			y: 52,
			w: 3,
			h: 12
		}
	}],
	legR: [{
		l: {
			x: 8,
			y: 20,
			w: 4,
			h: 12
		},
		r: {
			x: 0,
			y: 20,
			w: 4,
			h: 12
		},
		u: {
			x: 4,
			y: 16,
			w: 4,
			h: 4
		},
		d: {
			x: 8,
			y: 19,
			w: 4,
			h: -4
		},
		f: {
			x: 4,
			y: 20,
			w: 4,
			h: 12
		},
		b: {
			x: 12,
			y: 20,
			w: 4,
			h: 12
		}
	},
	{
		l: {
			x: 8,
			y: 36,
			w: 4,
			h: 12
		},
		r: {
			x: 0,
			y: 36,
			w: 4,
			h: 12
		},
		u: {
			x: 4,
			y: 32,
			w: 4,
			h: 4
		},
		d: {
			x: 8,
			y: 35,
			w: 4,
			h: -4
		},
		f: {
			x: 4,
			y: 36,
			w: 4,
			h: 12
		},
		b: {
			x: 12,
			y: 36,
			w: 4,
			h: 12
		}
	}],
	legL: [{
		l: {
			x: 24,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 16,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 20,
			y: 48,
			w: 4,
			h: 4
		},
		d: {
			x: 24,
			y: 51,
			w: 4,
			h: -4
		},
		f: {
			x: 20,
			y: 52,
			w: 4,
			h: 12
		},
		b: {
			x: 28,
			y: 52,
			w: 4,
			h: 12
		}
	},
	{
		l: {
			x: 8,
			y: 52,
			w: 4,
			h: 12
		},
		r: {
			x: 0,
			y: 52,
			w: 4,
			h: 12
		},
		u: {
			x: 4,
			y: 48,
			w: 4,
			h: 4
		},
		d: {
			x: 8,
			y: 51,
			w: 4,
			h: -4
		},
		f: {
			x: 4,
			y: 52,
			w: 4,
			h: 12
		},
		b: {
			x: 12,
			y: 52,
			w: 4,
			h: 12
		}
	}]
}];
function radians(d) {
	return d * (TAU / 360);
}
function textureUrl(hash) {
	return 'https://texture.namemc.com/' + hash[0] + hash[1] + '/' + hash[2] + hash[3] + '/' + hash + '.png';
}
function toCanvas(image, x, y, w, h) {
	x = (typeof x === 'undefined' ? 0 : x);
	y = (typeof y === 'undefined' ? 0 : y);
	w = (typeof w === 'undefined' ? image.width : w);
	h = (typeof h === 'undefined' ? image.height : h);
	let canvas = document.createElement('canvas');
	canvas.width = w;
	canvas.height = h;
	let ctx = canvas.getContext('2d');
	ctx.drawImage(image, x, y, w, h, 0, 0, w, h);
	return canvas;
}
function makeOpaque(image) {
	let canvas = toCanvas(image);
	let ctx = canvas.getContext('2d');
	let data = ctx.getImageData(0, 0, canvas.width, canvas.height);
	let pixels = data.data;
	for(let p = 3; p < pixels.length; p += 4) {
		pixels[p] = 255;
	}
	ctx.putImageData(data, 0, 0);
	return canvas;
}
function hasAlphaLayer(image) {
	let canvas = toCanvas(image);
	let ctx = canvas.getContext('2d');
	let data = ctx.getImageData(0, 0, canvas.width, canvas.height);
	let pixels = data.data;
	for(let p = 3; p < pixels.length; p += 4) {
		if(pixels[p] !== 255) {
			return true;
		}
	}
	return false;
}
function capeScale(height) {
	if(height % 22 === 0) {
		return height / 22;
	} else if(height % 17 === 0) {
		return height / 17;
	} else if(height >= 32 && (height & (height - 1)) === 0) {
		return height / 32;
	} else {
		return Math.max(1, Math.floor(height / 22));
	}
}
function drawSkin2D() {
	$('canvas.skin-2d').each(function(i, e) {
		let url = textureUrl(e.getAttribute('data-skin-hash'));
		let flip = e.getAttribute('data-flip') === 'true';
		let image = new Image();
		image.crossOrigin = '';
		image.src = url;
		image.onload = function() {
			let opaque = makeOpaque(image);
			let ctx = e.getContext('2d');
			ctx.mozImageSmoothingEnabled = false;
			ctx.webkitImageSmoothingEnabled = false;
			ctx.msImageSmoothingEnabled = false;
			ctx.imageSmoothingEnabled = false;
			if(flip) {
				ctx.translate(e.width, e.height);
				ctx.scale(- 1, -1);
			}
			ctx.drawImage(opaque, 8, 8, 8, 8, 0, 0, e.width, e.height);
			if(hasAlphaLayer(image)) {
				ctx.drawImage(image, 40, 8, 8, 8, 0, 0, e.width, e.height);
			}
		};
		image.onerror = function() {
			console.error('Error loading ' + image.src);
		};
	});
	$('canvas.cape-2d').each(function(i, e) {
		let url = textureUrl(e.getAttribute('data-cape-hash'));
		let flip = e.getAttribute('data-flip') === 'true';
		let image = new Image();
		image.crossOrigin = '';
		image.src = url;
		image.onload = function() {
			let cs = image ? capeScale(image.height) : null;
			let opaque = makeOpaque(image);
			let ctx = e.getContext('2d');
			ctx.mozImageSmoothingEnabled = false;
			ctx.webkitImageSmoothingEnabled = false;
			ctx.msImageSmoothingEnabled = false;
			ctx.imageSmoothingEnabled = false;
			if(flip) {
				ctx.translate(e.width, e.height);
				ctx.scale(- 1, -1);
			}
			ctx.drawImage(opaque, cs, cs, 10 * cs, 16 * cs, 0, 0, e.width, e.height);
		};
		image.onerror = function() {
			console.error('Error loading ' + image.src);
		};
	});
}
function colorFaces(geometry, canvas, rectangles) {
	if(!rectangles) return null;
	let pixels = canvas.getContext('2d').getImageData(0, 0, canvas.width, canvas.height).data;
	let f = 0;
	let faces = [];
	let materials = [];
	let materialIndexMap = {};
	let side = THREE.FrontSide;
	Object.keys(rectangles).forEach(function(k) {
		let rect = rectangles[k];
		let width = Math.abs(rect.w);
		let height = Math.abs(rect.h);
		let dj = Math.sign(rect.w);
		let di = Math.sign(rect.h);
		for(let y = 0, i = rect.y; y < height; y++, i += di) {
			for(let x = 0, j = rect.x; x < width; x++, j += dj, f += 2) {
				let p = 4 * (i * canvas.width + j);
				let a = pixels[p + 3];
				if(a === 0) {
					side = THREE.DoubleSide;
					continue;
				}
				let materialIndex = materialIndexMap[a];
				if(typeof materialIndex === 'undefined') {
					materials.push(new THREE.MeshLambertMaterial({
						vertexColors: THREE.FaceColors,
						opacity: a / 255,
						transparent: (a !== 255)
					}));
					materialIndex = materials.length - 1;
					materialIndexMap[a] = materialIndex;
					if(a !== 255) {
						side = THREE.DoubleSide;
					}
				}
				let face1 = geometry.faces[f];
				let face2 = geometry.faces[f + 1];
				face1.color.r = pixels[p] / 255;
				face1.color.g = pixels[p + 1] / 255;
				face1.color.b = pixels[p + 2] / 255;
				face2.color = face1.color;
				face1.materialIndex = materialIndex;
				face2.materialIndex = materialIndex;
				faces.push(face1);
				faces.push(face2);
			}
		}
	});
	if(faces.length === 0) return null;
	geometry.faces = faces;
	materials.forEach(function(m) {
		m.side = side;
	});
	return new THREE.Mesh(new THREE.BufferGeometry().fromGeometry(geometry), materials);
}
function buildMinecraftModel(skinImage, capeImage, slim, flip) {
	if(skinImage.width < 64 || skinImage.height < 32) {
		return null;
	}
	let version = (skinImage.height >= 64 ? 1 : 0);
	let cs = capeImage ? capeScale(capeImage.height) : null;
	let opaqueSkinCanvas = makeOpaque(skinImage);
	let transparentSkinCanvas = toCanvas(skinImage);
	let hasAlpha = hasAlphaLayer(skinImage);
	let headGroup = new THREE.Object3D();
	headGroup.position.x = 0;
	headGroup.position.y = 12;
	headGroup.position.z = 0;
	let box = new THREE.BoxGeometry(8, 8, 8, 8, 8, 8);
	let headMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['head'][0]);
	headGroup.add(headMesh);
	if(hasAlpha) {
		box = new THREE.BoxGeometry(9, 9, 9, 8, 8, 8);
		let hatMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['head'][1]);
		hatMesh && headGroup.add(hatMesh);
	}
	let torsoGroup = new THREE.Object3D();
	torsoGroup.position.x = 0;
	torsoGroup.position.y = 2;
	torsoGroup.position.z = 0;
	box = new THREE.BoxGeometry(8 + EPSILON, 12 + EPSILON, 4 + EPSILON, 8, 12, 4);
	let torsoMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['torso'][0]);
	torsoGroup.add(torsoMesh);
	if(version >= 1 && hasAlpha) {
		box = new THREE.BoxGeometry(8.5 + EPSILON, 12.5 + EPSILON, 4.5 + EPSILON, 8, 12, 4);
		let jacketMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['torso'][1]);
		jacketMesh && torsoGroup.add(jacketMesh);
	}
	let rightArmGroup = new THREE.Object3D();
	rightArmGroup.position.x = slim ? -5.5 : -6;
	rightArmGroup.position.y = 6;
	rightArmGroup.position.z = 0;
	let rightArmMesh;
	if(slim) {
		box = new THREE.BoxGeometry(3, 12, 4, 3, 12, 4).translate(0, -4, 0);
		rightArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['armRS'][0]);
	} else {
		box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -4, 0);
		rightArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['armR'][0]);
	}
	rightArmGroup.add(rightArmMesh);
	if(version >= 1 && hasAlpha) {
		let rightSleeveMesh;
		if(slim) {
			box = new THREE.BoxGeometry(3.5 + EPSILON * 4, 12.5 + EPSILON * 4, 4.5 + EPSILON * 4, 3, 12, 4).translate(0, -4, 0);
			rightSleeveMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['armRS'][1]);
		} else {
			box = new THREE.BoxGeometry(4.5 + EPSILON * 4, 12.5 + EPSILON * 4, 4.5 + EPSILON * 4, 4, 12, 4).translate(0, -4, 0);
			rightSleeveMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['armR'][1]);
		}
		rightSleeveMesh && rightArmGroup.add(rightSleeveMesh);
	}
	let leftArmGroup = new THREE.Object3D();
	leftArmGroup.position.x = slim ? 5.5 : 6;
	leftArmGroup.position.y = 6;
	leftArmGroup.position.z = 0;
	let leftArmMesh;
	if(slim) {
		box = new THREE.BoxGeometry(3, 12, 4, 3, 12, 4).translate(0, -4, 0);
		leftArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['armLS'][0]);
	} else {
		box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -4, 0);
		leftArmMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['armL'][0]);
	}
	leftArmGroup.add(leftArmMesh);
	if(version >= 1 && hasAlpha) {
		let leftSleeveMesh;
		if(slim) {
			box = new THREE.BoxGeometry(3.5 + EPSILON * 4, 12.5 + EPSILON * 4, 4.5 + EPSILON * 4, 3, 12, 4).translate(0, -4, 0);
			leftSleeveMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['armLS'][1]);
		} else {
			box = new THREE.BoxGeometry(4.5 + EPSILON * 4, 12.5 + EPSILON * 4, 4.5 + EPSILON * 4, 4, 12, 4).translate(0, -4, 0);
			leftSleeveMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['armL'][1]);
		}
		leftSleeveMesh && leftArmGroup.add(leftSleeveMesh);
	}
	let rightLegGroup = new THREE.Object3D();
	rightLegGroup.position.x = -2;
	rightLegGroup.position.y = -4;
	rightLegGroup.position.z = 0;
	box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -6, 0);
	let rightLegMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['legR'][0]);
	rightLegGroup.add(rightLegMesh);
	if(version >= 1 && hasAlpha) {
		box = new THREE.BoxGeometry(4.5 + EPSILON * 2, 12.5 + EPSILON * 2, 4.5 + EPSILON * 2, 4, 12, 4).translate(0, -6, 0);
		let rightPantMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['legR'][1]);
		rightPantMesh && rightLegGroup.add(rightPantMesh);
	}
	let leftLegGroup = new THREE.Object3D();
	leftLegGroup.position.x = 2;
	leftLegGroup.position.y = -4;
	leftLegGroup.position.z = 0;
	box = new THREE.BoxGeometry(4, 12, 4, 4, 12, 4).translate(0, -6, 0);
	let leftLegMesh = colorFaces(box, opaqueSkinCanvas, skinLayout[version]['legL'][0]);
	leftLegGroup.add(leftLegMesh);
	if(version >= 1 && hasAlpha) {
		box = new THREE.BoxGeometry(4.5 + EPSILON * 3, 12.5 + EPSILON * 3, 4.5 + EPSILON * 3, 4, 12, 4).translate(0, -6, 0);
		let leftPantMesh = colorFaces(box, transparentSkinCanvas, skinLayout[version]['legL'][1]);
		leftPantMesh && leftLegGroup.add(leftPantMesh);
	}
	let playerGroup = new THREE.Object3D();
	playerGroup.add(headGroup);
	playerGroup.add(torsoGroup);
	playerGroup.add(rightArmGroup);
	playerGroup.add(leftArmGroup);
	playerGroup.add(rightLegGroup);
	playerGroup.add(leftLegGroup);
	if(capeImage) {
		let capeCanvas = makeOpaque(capeImage);
		let capeGroup = new THREE.Object3D();
		capeGroup.position.x = 0;
		capeGroup.position.y = 8;
		capeGroup.position.z = -2;
		capeGroup.rotation.y += radians(180);
		let capeMesh;
		box = new THREE.BoxGeometry(10, 16, 1, 10 * cs, 16 * cs, cs).translate(0, -8, 0.5);
		capeMesh = colorFaces(box, capeCanvas, {
			left: {
				x: 11 * cs,
				y: cs,
				w: cs,
				h: 16 * cs
			},
			right: {
				x: 0,
				y: cs,
				w: cs,
				h: 16 * cs
			},
			top: {
				x: cs,
				y: 0,
				w: 10 * cs,
				h: cs
			},
			bottom: {
				x: 11 * cs,
				y: cs - 1,
				w: 10 * cs,
				h: -cs
			},
			front: {
				x: cs,
				y: cs,
				w: 10 * cs,
				h: 16 * cs
			},
			back: {
				x: 12 * cs,
				y: cs,
				w: 10 * cs,
				h: 16 * cs
			}
		});
		capeGroup.add(capeMesh);
		playerGroup.add(capeGroup);
	}
	if(flip) {
		playerGroup.rotation.z += radians(180);
	}
	return playerGroup;
}
let renderState;
let renderButton = $('#render-button');
let skinButtons = $('.skin-button');
let canvas3d = $('canvas.skin-3d');
function animateSkin(e, toggle) {
	renderState.animate = getCookie('animate') === 'true';
	if(toggle) {
		renderState.animate = !renderState.animate;
		setCookie('animate', renderState.animate.toString());
	}
	if(renderState.animate) {
		e.children[0].classList.remove('fa-play');
		e.children[0].classList.add('fa-pause');
		canvas3d.parent().removeClass('animation-paused');
		renderState.timestamp = Date.now();
		window.requestAnimationFrame(renderAnimation);
	} else {
		e.children[0].classList.remove('fa-pause');
		e.children[0].classList.add('fa-play');
		canvas3d.parent().addClass('animation-paused');
	}
}
function renderAnimation() {
	if(renderState.animate) {
		let now = Date.now();
		renderState.time += (now - renderState.timestamp) * (360 / 1500);
		renderState.timestamp = now;
		render();
		window.requestAnimationFrame(renderAnimation);
	}
}
function render() {
	canvas3d.attr('data-time', Math.round(renderState.time) % 360);
	renderButton.attr('href', 'https://render.namemc.com/skin/3d/body.png?' + 'skin=' + canvas3d.attr('data-skin-hash') + '&model=' + canvas3d.attr('data-model') + '&theta=' + canvas3d.attr('data-theta') + '&phi=' + canvas3d.attr('data-phi') + '&time=' + canvas3d.attr('data-time') + '&width=600' + '&height=800');
	let angle = Math.sin(radians(renderState.time));
	renderState.model.children[2].rotation.x = -radians(18) * angle;
	renderState.model.children[3].rotation.x = radians(18) * angle;
	renderState.model.children[4].rotation.x = radians(20) * angle;
	renderState.model.children[5].rotation.x = -radians(20) * angle;
	if(renderState.model.children[6]) {
		let capeAngle = Math.sin(radians(renderState.time / 4));
		renderState.model.children[6].rotation.x = radians(18) - radians(6) * capeAngle;
	}
	renderState.renderer.render(renderState.scene, renderState.camera);
	if(renderState.canvas !== renderState.renderer.domElement) {
		renderState.canvas.getContext('2d').drawImage(renderState.renderer.domElement, 0, 0);
	}
}
function enableRotation(renderState) {
	function startRotation(t, id) {
		renderState.dragState[id] = {
			x: t.screenX,
			y: t.screenY
		};
	}
	function rotate(t, id) {
		if(!renderState.dragState[id]) {
			return false;
		}
		let result = true;
		renderState.theta += t.screenX - renderState.dragState[id].x;
		renderState.phi += t.screenY - renderState.dragState[id].y;
		renderState.canvas.setAttribute('data-theta', (renderState.theta % 360).toString());
		renderState.canvas.setAttribute('data-phi', (renderState.phi % 360).toString());
		renderButton.attr('href', 'https://render.namemc.com/skin/3d/body.png?' + 'skin=' + canvas3d.attr('data-skin-hash') + '&model=' + canvas3d.attr('data-model') + '&theta=' + canvas3d.attr('data-theta') + '&phi=' + canvas3d.attr('data-phi') + '&time=' + canvas3d.attr('data-time') + '&width=600' + '&height=800');
		if(renderState.phi < -90) {
			renderState.phi = -90;
			result = false;
		} else if(renderState.phi > 90) {
			renderState.phi = 90;
			result = false;
		}
		renderState.model.rotation.y = radians(renderState.theta);
		renderState.model.rotation.x = radians(renderState.phi);
		renderState.renderer.render(renderState.scene, renderState.camera);
		renderState.dragState[id].x = t.screenX;
		renderState.dragState[id].y = t.screenY;
		return result;
	}
	function endRotation(t, id) {
		delete renderState.dragState[id];
	}
	renderState.canvas.onmousedown = function(e) {
		e.preventDefault();
		startRotation(e, 'mouse');
	};
	window.onmousemove = function(e) {
		rotate(e, 'mouse');
	};
	window.onmouseup = function(e) {
		endRotation(e, 'mouse');
	};
	renderState.canvas.ontouchstart = function(e) {
		for(let i = 0; i < e.changedTouches.length; i++) {
			startRotation(e.changedTouches[i], e.changedTouches[i].identifier);
		}
	};
	renderState.canvas.ontouchmove = function(e) {
		let result = false;
		for(let i = 0; i < e.changedTouches.length; i++) {
			if(rotate(e.changedTouches[i], e.changedTouches[i].identifier)) {
				result = true;
			} else {
				delete renderState.dragState[e.changedTouches[i].identifier];
			}
		}
		if(result) {
			e.preventDefault();
		}
	};
	renderState.canvas.ontouchend = renderState.canvas.ontouchcancel = function(e) {
		for(let i = 0; i < e.changedTouches.length; i++) {
			endRotation(e.changedTouches[i], e.changedTouches[i].identifier);
		}
	};
}
let renderer;
function renderSkinHelper(canvas, animate, theta, phi, time, model) {
	if(renderState) {
		renderState.canvas = canvas;
		renderState.scene.remove(renderState.model);
		renderState.model = model;
		renderState.scene.add(model);
		renderState.animate = animate;
		renderState.model.rotation.y = radians(theta);
		renderState.model.rotation.x = radians(phi);
		render();
		return;
	}
	if(!renderer) {
		renderer = new THREE.WebGLRenderer({
			canvas: canvas,
			alpha: true,
			antialias: true
		});
	}
	renderState = {
		canvas: canvas,
		animate: animate,
		model: model,
		theta: theta,
		phi: phi,
		scene: new THREE.Scene(),
		camera: new THREE.PerspectiveCamera(38, canvas.width / canvas.height, 60 - 20, 60 + 20),
		renderer: renderer,
		dragState: {},
		time: time
	};
	renderState.camera.position.x = 0;
	renderState.camera.position.z = 60;
	renderState.camera.position.y = 0;
	renderState.camera.lookAt(new THREE.Vector3(0, 0, 0));
	renderState.scene.add(model);
	let ambLight = new THREE.AmbientLight(0xFFFFFF, 0.7);
	let dirLight = new THREE.DirectionalLight(0xFFFFFF, 0.3);
	dirLight.position.set(0.67763, 0.28571, 0.67763);
	renderState.scene.add(ambLight);
	renderState.scene.add(dirLight);
	renderState.model.rotation.y = radians(theta);
	renderState.model.rotation.x = radians(phi);
	enableRotation(renderState);
	render();
	if(renderState.animate) {
		for(let e of document.getElementsByClassName('play-pause-btn')) {
			animateSkin(e);
		}
	}
}
let modelCache = {};
function renderSkin(canvas, slim, flip, animate, theta, phi, time, skinHash, capeHash, callback) {
	let hash = [capeHash, skinHash, slim, flip].join(':');
	function handleModel() {
		try {
			renderSkinHelper(canvas, animate, theta, phi, time, modelCache[hash]);
			callback();
		} catch(e) {
			callback(e);
		}
	}
	if(modelCache[hash]) {
		handleModel();
	} else {
		function handleImages(skinImage, capeImage) {
			let model = buildMinecraftModel(skinImage, capeImage, slim, flip);
			if(model) {
				modelCache[hash] = model;
				handleModel();
			} else {
				callback();
			}
		}
		let skinImage = new Image();
		skinImage.crossOrigin = '';
		skinImage.src = textureUrl(skinHash);
		skinImage.onload = function() {
			if(capeHash) {
				let capeImage = new Image();
				capeImage.crossOrigin = '';
				capeImage.src = textureUrl(capeHash);
				capeImage.onload = function() {
					handleImages(skinImage, capeImage);
				};
				capeImage.onerror = function() {
					handleImages(skinImage, null);
					console.error('Error loading ' + capeImage.src);
				};
			} else {
				handleImages(skinImage, null);
			}
		};
		skinImage.onerror = function() {
			console.error('Error loading ' + skinImage.src);
		};
	}
}
function drawFullSkin2D(e) {
	let isCape = e.classList.contains('cape-3d');
	let skinHash = e.getAttribute('data-skin-hash');
	let capeHash = e.getAttribute('data-cape-hash');
	let url = textureUrl(isCape ? capeHash : skinHash);
	let flip = e.getAttribute('data-flip') === 'true';
	let image = new Image();
	image.crossOrigin = '';
	image.src = url;
	image.onload = function() {
		let opaque = makeOpaque(image);
		let ctx = e.getContext('2d');
		ctx.save();
		ctx.mozImageSmoothingEnabled = false;
		ctx.webkitImageSmoothingEnabled = false;
		ctx.msImageSmoothingEnabled = false;
		ctx.imageSmoothingEnabled = false;
		if(flip) {
			ctx.translate(e.width, e.height);
			ctx.scale(- 1, -1);
		}
		ctx.translate(e.width / 2, e.height / 2);
		let scale;
		if(isCape) {
			scale = Math.min(Math.floor(e.width / 10), Math.floor(e.height / 16)) - 1;
			ctx.scale(scale, scale);
			ctx.drawImage(opaque, 1, 1, 10, 16, -5, -8, 10, 16);
		} else {
			scale = Math.min(Math.floor(e.width / 16), Math.floor(e.height / 32)) - 1;
			ctx.scale(scale, scale);
			ctx.drawImage(opaque, 8, 8, 8, 8, -4, -16, 8, 8);
			ctx.drawImage(opaque, 20, 20, 8, 12, -4, -8, 8, 12);
			ctx.drawImage(opaque, 44, 20, 4, 12, -8, -8, 4, 12);
			let version = (image.height >= 64 ? 1 : 0);
			if(version === 0) {
				ctx.save();
				ctx.scale(- 1, 1);
				ctx.drawImage(opaque, 44, 20, 4, 12, -8, -8, 4, 12);
				ctx.restore();
			} else {
				ctx.drawImage(opaque, 36, 52, 4, 12, 4, -8, 4, 12);
			}
			ctx.drawImage(opaque, 4, 20, 4, 12, -4, 4, 4, 12);
			if(version === 0) {
				ctx.save();
				ctx.scale(- 1, 1);
				ctx.drawImage(opaque, 4, 20, 4, 12, -4, 4, 4, 12);
				ctx.restore();
			} else {
				ctx.drawImage(opaque, 20, 52, 4, 12, 0, 4, 4, 12);
			}
			if(hasAlphaLayer(image)) {
				ctx.drawImage(image, 40, 8, 8, 8, -4, -16, 8, 8);
				if(version >= 1) {
					ctx.drawImage(image, 20, 36, 8, 12, -4, -8, 8, 12);
					ctx.drawImage(image, 44, 36, 4, 12, -8, -8, 4, 12);
					ctx.drawImage(image, 52, 52, 4, 12, 4, -8, 4, 12);
					ctx.drawImage(image, 4, 36, 4, 12, -4, 4, 4, 12);
					ctx.drawImage(image, 4, 52, 4, 12, 0, 4, 4, 12);
				}
			}
		}
		ctx.restore();
	};
	image.onerror = function() {
		console.error('Error loading ' + image.src);
	};
}
function drawSkin3D() {
	if(!canvas3d.get(0)) return;
	let slim = canvas3d.attr('data-model') === 'slim';
	let skinHash = canvas3d.attr('data-skin-hash');
	let capeHash = canvas3d.attr('data-cape-hash');
	let flip = canvas3d.attr('data-flip') === 'true';
	let animate = getCookie('animate') === 'true';
	let theta = canvas3d.attr('data-theta') ? parseFloat(canvas3d.attr('data-theta')) : 30;
	let phi = canvas3d.attr('data-phi') ? parseFloat(canvas3d.attr('data-phi')) : 21;
	let time = canvas3d.attr('data-time') ? parseFloat(canvas3d.attr('data-time')) : 90;
	canvas3d.attr('data-model', slim ? 'slim' : 'classic');
	canvas3d.attr('data-theta', theta);
	canvas3d.attr('data-phi', phi);
	canvas3d.attr('data-time', time);
	renderSkin(canvas3d.get(0), slim, flip, animate, theta, phi, time, skinHash, capeHash,
		function(err) {
			if(err) {
				$('.play-pause-btn').remove();
				drawFullSkin2D(canvas3d.get(0));
			}
		});
}
function updateSkin(element) {
	let skinHash = element.getAttribute('data-skin-hash');
	let capeHash = element.getAttribute('data-cape-hash');
	let model = element.getAttribute('data-model');
	let redraw = false;
	if(skinHash && canvas3d.attr('data-skin-hash') !== skinHash) {
		canvas3d.attr('data-skin-hash', skinHash);
		redraw = true;
	}
	if(capeHash && canvas3d.attr('data-cape-hash') !== capeHash) {
		canvas3d.attr('data-cape-hash', capeHash);
		redraw = true;
	}
	if(model && canvas3d.attr('data-model') !== model) {
		canvas3d.attr('data-model', model);
		redraw = true;
	}
	if(redraw) {
		drawSkin3D();
		skinButtons.each(function(i, e) {
			$(e).toggleClass('skin-button-selected', $(e).attr('data-skin-hash') === canvas3d.attr('data-skin-hash') || $(e).attr('data-cape-hash') === canvas3d.attr('data-cape-hash'));
		});
	}
	return false;
}
skinButtons.each(function(i, e) {
	if('onpointermove' in e) {
		e.onpointermove = function() {
			updateSkin(e);
		};
	} else if('onmousemove' in e) {
		e.onmousemove = function() {
			updateSkin(e);
		};
	}
});
function scaleSkinsToDevice() {
	if(typeof window.devicePixelRatio === 'number' && window.devicePixelRatio !== 1.0) {
		let ratio = Math.min(2.0, window.devicePixelRatio);
		canvas3d.each(function(i, e) {
			e.width *= ratio;
			e.height *= ratio;
		});
		ratio = Math.max(1.0, Math.min(1.5, Math.round(window.devicePixelRatio * 4) / 4));
		$('img[data-src]').each(function(i, e) {
			let src = e.getAttribute('data-src');
			if(src.startsWith('https://render.namemc.com/skin/3d/')) {
				src = src.replace(/width=[0-9]+/, 'width=' + Math.round(e.width * ratio));
				src = src.replace(/height=[0-9]+/, 'height=' + Math.round(e.height * ratio));
			}
			e.src = src;
		});
	} else {
		$('img[data-src]').each(function(i, e) {
			e.src = e.getAttribute('data-src');
		});
	}
}
scaleSkinsToDevice();
drawSkin3D();
drawSkin2D();